package com.imarad.server.nio.handle;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.imarad.server.common.Constants;

/**
 * @author hhu 【huan.hu@cnambition.com】
 * @version com.imarad.demo, v 0.1 2018/1/30 15:18 hhu Exp $$
 */
public class NioServer implements Runnable {
    /**
     * 日志工具
     */
    private static final Logger        LOG         = LoggerFactory.getLogger(NioServer.class);
    /**
     * 端口号
     */
    private int                        port;
    /**
     * 发送数据缓冲区
     */
    private ByteBuffer                 writeBuffer = ByteBuffer
        .allocate(Constants.RECEVIE_SOCKET_PACKET_TOTAL_LEN);
    /**
     * 接受数据缓冲区
     */
    private ByteBuffer                 readBuffer  = ByteBuffer
        .allocate(Constants.RECEVIE_SOCKET_PACKET_TOTAL_LEN);
    /**
     * 映射客户端channel
     */
    private Map<String, ServerHandler> clientsMap  = new HashMap<>();
    /**
     * 轮询器
     */
    private Selector                   selector;
    /**
     * 服务端启动状态
     */
    private boolean                    isRun;

    private ExecutorService            poll;

    public NioServer(int port) {
        this.port = port;
        this.isRun = true;
        this.poll = new ThreadPoolExecutor(5, 50, 200, TimeUnit.MILLISECONDS,
            new ArrayBlockingQueue<>(50), new ServerThreadFactory());
    }

    /**
     * 服务器端轮询监听，select方法会一直阻塞直到有相关事件发生或超时
     */
    @Override
    public void run() {
        Thread.currentThread().setName("NIOServerThread");
        /*
         * 启动服务器端，配置为非阻塞，绑定端口，注册accept事件
         * ACCEPT事件：当服务端收到客户端连接请求时，触发该事件
         */
        try (ServerSocketChannel serverSocketChannel = ServerSocketChannel.open()) {
            serverSocketChannel.configureBlocking(false);
            ServerSocket serverSocket = serverSocketChannel.socket();
            serverSocket.bind(new InetSocketAddress(port), 1024);
            selector = Selector.open();
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            LOG.info("服务器启动成功,服务端口:{}", port);

            while (isRun) {
                //返回值为本次触发的事件数,在这里阻塞
                int n = selector.select();
                if (n == 0) {
                    continue;
                }
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                LOG.debug("轮询事件数量：{}", selector.selectedKeys().size());
                Iterator<SelectionKey> it = selectionKeys.iterator();
                SelectionKey key = null;
                try {
                    while (it.hasNext()) {
                        key = it.next();
                        handle(key);
                    }
                } catch (Exception e) {
                    if ("远程主机强迫关闭了一个现有的连接。".equals(e.getLocalizedMessage()) && key != null) {
                        LOG.debug("{}已经自动断开连接", makeClientKey((SocketChannel) key.channel()));
                    } else {
                        LOG.error("服务端异常:", e);
                    }
                    //注销掉
                    if (key != null) {
                        key.cancel();
                    }
                }
                selectionKeys.clear();
            }

        } catch (IOException e) {
            LOG.error("服务端异常:", e);
            isRun = false;
        }
    }

    /**
     * 处理不同的事件
     */
    private void handle(SelectionKey selectionKey) throws IOException, InterruptedException {
        ServerSocketChannel server;
        SocketChannel client;

        if (selectionKey.isAcceptable()) {
            /*
             * 客户端请求连接事件
             * ServerSocketChannel为该客户端建立socket连接，将此socket注册READ事件，监听客户端输入
             * READ事件：当客户端发来数据，并已被服务器控制线程正确读取时，触发该事件
             */
            server = (ServerSocketChannel) selectionKey.channel();
            client = server.accept();
            client.configureBlocking(false);
            client.register(selector, SelectionKey.OP_READ);
            ServerHandler handler = new ServerHandler(client);
            poll.execute(handler);
            clientsMap.put(makeClientKey(client), handler);
            LOG.info("接入客户端：{}", makeClientKey(client));
        } else if (selectionKey.isReadable()) {
            /*
             * READ事件，收到客户端发送数据，读取数据后继续注册监听客户端
             */
            client = (SocketChannel) selectionKey.channel();
            LOG.debug(
                "SocketChannel:[isConnected={}, isConnectionPending={}, isRegistered={}, isOpen={}, isBlocking={}]",
                client.isConnected(), client.isConnectionPending(), client.isRegistered(),
                client.isOpen(), client.isBlocking());
            if (client.isConnected()) {
                readBuffer.clear();
                client.read(readBuffer);
                readBuffer.flip();
                ServerHandler handler = clientsMap.get(makeClientKey(client));
                handler.receiveData(readBuffer.array());
            }
        }

    }

    /**
     * 构造客户端key
     *
     * @param channel
     * @return
     */
    private String makeClientKey(SocketChannel channel) {
        String key = "";
        try {
            InetSocketAddress clientAddress = (InetSocketAddress) channel.getRemoteAddress();
            key = "Client[host=" + clientAddress.getHostName() + ", ip="
                  + clientAddress.getAddress().getHostAddress() + ", port="
                  + clientAddress.getPort() + "]";
        } catch (IOException e) {
            e.printStackTrace();
        }
        if (StringUtils.isEmpty(key)) {
            key = "Client_" + System.nanoTime();
        }
        return key;
    }

    /**
     * 把当前客户端信息 推送到其他客户端
     */
    private void dispatch(SocketChannel client, String info) throws IOException {
        //
        //        Socket s = client.socket();
        //        String name = "[" + s.getInetAddress().toString().substring(1) + ":"
        //                      + Integer.toHexString(client.hashCode()) + "]";
        //        if (!clientsMap.isEmpty()) {
        //            for (Map.Entry<String, SocketChannel> entry : clientsMap.entrySet()) {
        //                SocketChannel temp = entry.getValue();
        //                if (!client.equals(temp)) {
        //                    sBuffer.clear();
        //                    sBuffer.put((name + ":" + info).getBytes());
        //                    sBuffer.flip();
        //                    //输出到通道
        //                    temp.write(sBuffer);
        //                }
        //            }
        //        }
        //        clientsMap.put(name, client);
    }
}